VOIP/Video Consultations
VoipConfig Object
For VOIP and VIDEO consultations, the consultation data will include the relevant configuration object:
voipConfig: Used for VOIP (audio-only) calls.videoConfig: Used for VIDEO calls.
Both use the same data structure:
class VoipConfig {
int? id;
int? consultationId;
String? apiKey;
String? callId;
String? token;
}
VOIP vs Video handling
VOIP and Video consultations are handled using the same VideoController and VideoView components. For VOIP calls, you should simply call _controller.toggleVideo(false) after the session starts to keep only the audio active.
VOIP/Video Implementation Example
Below is a complete example of a page that handles a VOIP or Video consultation:
import 'package:flutter/material.dart';
import 'package:altibbi/altibbi_video.dart' as open_tok;
import 'package:altibbi/video_view.dart';
import 'package:altibbi/service/api_service.dart';
import 'package:permission_handler/permission_handler.dart';
class ConsultationCallPage extends StatefulWidget {
final open_tok.VideoConfig config;
final int consultationId;
final bool isVoipOnly;
const ConsultationCallPage({
super.key,
required this.config,
required this.consultationId,
required this.isVoipOnly,
});
@override
State<ConsultationCallPage> createState() => _ConsultationCallPageState();
}
class _ConsultationCallPageState extends State<ConsultationCallPage> {
final VideoController _controller = VideoController();
bool _audioEnabled = true;
bool _videoEnabled = true;
@override
void initState() {
super.initState();
_setupController();
_requestPermissionsAndInit();
}
void _setupController() {
_controller.stateUpdateCallBack = (stateUpdate) {
if (stateUpdate.state == open_tok.ConnectionState.loggedIn) {
if (widget.isVoipOnly) {
_controller.toggleVideo(false);
setState(() => _videoEnabled = false);
}
}
};
}
Future<void> _requestPermissionsAndInit() async {
Map<Permission, PermissionStatus> statuses = await [
Permission.camera,
Permission.microphone,
].request();
if (statuses[Permission.camera]!.isGranted && statuses[Permission.microphone]!.isGranted) {
_controller.initSession(widget.config);
} else {
// Handle permission denied
}
}
void _endCall() async {
await _controller.endSession();
await ApiService().cancelConsultation(widget.consultationId);
Navigator.of(context).pop();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
// Background Video View
VideoView(controller: _controller),
// Call Controls
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FloatingActionButton(
onPressed: _endCall,
backgroundColor: Colors.red,
child: const Icon(Icons.call_end),
),
FloatingActionButton(
onPressed: () {
_audioEnabled = !_audioEnabled;
_controller.toggleAudio(_audioEnabled);
setState(() {});
},
child: Icon(_audioEnabled ? Icons.mic : Icons.mic_off),
),
if (!widget.isVoipOnly)
FloatingActionButton(
onPressed: () {
_videoEnabled = !_videoEnabled;
_controller.toggleVideo(_videoEnabled);
setState(() {});
},
child: Icon(_videoEnabled ? Icons.videocam : Icons.videocam_off),
),
FloatingActionButton(
onPressed: () => _controller.toggleCamera(),
child: const Icon(Icons.cameraswitch),
),
],
),
),
),
],
),
);
}
}